TUI warping indicator#13442
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
|
I'm starting a first review of this pull request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds the TUI warping indicator and the supporting repaint scheduling/animation plumbing across warp_tui and warpui_core, including shimmer math extraction and tests for the new TUI animation behavior.
Concerns
- No blocking correctness, security, error-handling, performance, or spec-alignment concerns found in the annotated diff.
- No approved or repository spec context was available for this PR, so there was no external spec drift to flag.
- The PR description includes a Loom link, satisfying the repository's visual-evidence requirement for this user-facing TUI change.
Verdict
Found: 0 critical, 0 important, 0 suggestions
Approve
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
37bafb8 to
cab95a5
Compare
21cc85f to
e516967
Compare
9656467 to
2ad9ea3
Compare
e516967 to
b28ed1e
Compare
2ad9ea3 to
4f8db0a
Compare
c2820d9 to
a56cce3
Compare
a56cce3 to
da5c961
Compare
kevinyang372
left a comment
There was a problem hiding this comment.
Nice -- had one question
| // but the caller drives the presenter standalone without the | ||
| // runtime's invalidate() step). | ||
| // 3. Direct render fallback for callers that skip invalidate(). | ||
| // 2. Cached last_element — ONLY when invalidate() ran this frame, so |
There was a problem hiding this comment.
This is a genuine behavior change yeah? This means if a view doesn't have animation AND no view was changed, we used to not repaint and reuse the last element tree. Now we are going to redraw for that case
There was a problem hiding this comment.
Is there anyway we could capture if this redraw is triggered by an animation?
There was a problem hiding this comment.
It is a real behavior change, but I think in the opposite direction from what you're saying. The old condition was just a bad proxy for what we actually wanted to check.
Before, step 2 reused last_element when rendered_views was non-empty. That was meant to mean "invalidate ran, so the cache is trustworthy", but it's the wrong proxy: rendered_views holds cached child-view elements, and step 1 just popped the root out. So:
- If the root has no child views, the map is empty at that point even though invalidate ran. Step 2 gets skipped and we fall to step 3, a full fresh render of the root view on every draw, pointlessly.
- If the root has child views, the map stays non-empty across frames (cached child elements get re-inserted after every use), so the check couldn't actually detect "invalidate didn't run", which is the case its own comment said it was guarding. In practice every caller pairs invalidate + present so that path is unreachable, but the proxy was measuring map state, not the thing we cared about.
The new flag checks the intended condition directly: reuse the cache iff invalidate() ran this frame.
The reason this came up while building animation was that an animation repaint is a timer-triggered draw where nothing changed. The draw still runs invalidate(), but with an empty set, so nothing gets re-rendered and the root is never in rendered_views. Step 2 is the only thing that lets us repaint the cached tree; under the old emptiness check, a childless root would have re-run its whole render() at the spinner's cadence, which is bad lol.
There was a problem hiding this comment.
and per the animation redraw capture, that should already be encoded. An animation-triggered draw is exactly a draw whose invalidation set is empty, so invalidate() re-renders nothing and present() reuses the cached tree. That reuse is the animation fast path. We could thread an explicit "draw reason" from the runtime, but it would just duplicate what the invalidation set already tells us.
3b42bf4 to
a984806
Compare
Rebased squash of the CODE-1831 branch onto latest master (which now includes the TUI warping indicator, #13442). Granular history including the preserved OSC 22 pointer implementation (348484d, referenced by CODE-1837) lives on the backup branch ian/code-1831-pre-rebase. The footer shows the selected conversation's GUI-consistent credits total after model + cwd, hidden until the first usage report; clicking toggles to the accumulated provider dollar cost and back; hover brightens. Credits come from the server's cumulative usage metadata formatted with the GUI's format_credits (exported through tui_export); cost sums the per-request StreamFinished usage rows. Includes the CODE-1828 tech spec. Co-Authored-By: Oz <oz-agent@warp.dev>

Description
The TUI should show a
⋮ Warping (Ns)indicator while the selected conversation is in progress (Figma): a rotating spinner, a shimmering "Warping" label, and a ticking elapsed counter, rendered between the transcript and the input box. We already had the shimmer math for free (the GUI'sShimmeringTextElement), but the TUI had no animation machinery at all — redraws are purely invalidation-driven — so the bulk of this PR is building the native repaint-scheduling mechanism (mirroring the GUI'sPaintContext::repaint_after) that the indicator then sits on top of. Animations run as paint-only redraws over the cached element tree, so nothing re-renders views or relayouts the transcript, and the TUI stays fully idle when nothing animates.The most important files to look at are:
crates/warpui_core/src/elements/tui/mod.rs— the newTuiPaintContextwithrepaint_after+ earliest-deadline-wins coalescing. This split (render/cursor now takeTuiPaintContextinstead ofTuiLayoutContext) is what causes the mechanical signature churn across every TUI element.crates/warpui_core/src/runtime/mod.rs— the driver side: each draw surfaces the frame's earliest requested repaint deadline and schedules a single one-shot timer for it, self-sustaining while anything animates.crates/warpui_core/src/elements/tui/animated.rs—TuiAnimated, the reusable animation element: a repaint cadence plus a closure that rebuilds the current frame each pass (drives the spinner and counter).crates/warpui_core/src/elements/tui/shimmering_text.rs+crates/warpui_core/src/elements/shimmer_math.rs— the TUI shimmering text and the band math extracted from the GUI element so both backends share it.crates/warp_tui/src/warping_indicator.rs— the indicator row itself: the spinner's keyframe choreography, the shimmer label, and the elapsed counter, all phase-locked to oneAnimationClock.crates/warp_tui/src/terminal_session_view.rs— visibility wiring: renders the row while the selected conversation is in progress, subscribed to history/selection events.Testing
./script/runScreenshots / Videos
https://www.loom.com/share/60f0cc8358c3431f8233ba95380292fe
Agent Mode